﻿using Sidvall.FileSystemManager.Data;
using Sidvall.FileSystemManager.Net.Services.V1;
using Sidvall.Serialization;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Xml.Linq;

namespace Sidvall.FileSystemManager
{
    public static class CodeManager
    {
        #region Public Members

        public const string SearchPatterns = "*.config|*.cs|*.sln|*.csproj|*.tt|*.xaml|*.bat|*.appxmanifest|*.cshtml|*.settings|*.asax|*.xml|*.txt";
        public const string SearchPatternsProjects = "*.csproj";
        public const string SearchPatternsCodeFiles = "*.cs";
        public const string SearchPatternSeparator = "|";
        public const string NumberPath = "{n}";
        public const int DefaultRetryCount = 20;

        #region CleanAsync

        public static async Task<CodeResult> CleanAsync(string folderPath, bool cleanPackageFolder, bool removeSourceControlLinks, bool cleanBuildFolders, bool cleanBackupFolders, int maxRetryCount)
        {
            var retryCount = 0;
            var codeResult = new CodeResult();
            do
            {
                try
                {
                    var folderPaths = (await GetSolutionFoldersAsync(folderPath).ConfigureAwait(false)).ToList();
                    if (folderPaths != null && folderPaths.Any())
                        await CleanAsync(folderPaths, cleanPackageFolder, removeSourceControlLinks, cleanBuildFolders, cleanBackupFolders, codeResult).ConfigureAwait(false);
                    else
                        await CleanAsync(folderPath, cleanPackageFolder, removeSourceControlLinks, cleanBuildFolders, cleanBackupFolders, codeResult).ConfigureAwait(false);
                    retryCount = maxRetryCount;
                }
                catch (System.IO.IOException)
                {
                    retryCount += 1;
                }
            } while (maxRetryCount > retryCount);
            return codeResult;
        }
        private static async Task CleanAsync(IEnumerable<string> folderPaths, bool cleanPackageFolder, bool removeSourceControlLinks, bool cleanBuildFolders, bool cleanBackupFolders, CodeResult codeResult)
        {
            if (folderPaths == null)
                return;
            foreach (var folderPath in folderPaths)
                await CleanAsync(folderPath, cleanPackageFolder, removeSourceControlLinks, cleanBuildFolders, cleanBackupFolders, codeResult).ConfigureAwait(false);
        }
        private static async Task CleanAsync(string folderPath, bool cleanPackageFolder, bool removeSourceControlLinks, bool cleanBuildFolders, bool cleanBackupFolders, CodeResult codeResult)
        {
            System.Collections.Generic.IEnumerable<string> files, subFolders;
            XDocument document;
            System.Collections.Generic.IEnumerable<XElement> elements;
            string fileFolder, text;
            int index1, index2;
            bool isUpdated;

            // Clean packages folder
            if (cleanPackageFolder)
            {
                subFolders = await Sidvall.SystemContext.Current.SystemStorageManager.GetFolderNamesAsync(folderPath, new string[] { "packages" }, Sidvall.IO.SearchOption.TopDirectoryOnly).ConfigureAwait(false);
                if (subFolders.Count() == 1)
                {
                    fileFolder = subFolders.First();
                    subFolders = await Sidvall.SystemContext.Current.SystemStorageManager.GetFolderNamesAsync(fileFolder, null, Sidvall.IO.SearchOption.TopDirectoryOnly).ConfigureAwait(false);
                    foreach (var subFolder in subFolders)
                    {
                        await Sidvall.SystemContext.Current.SystemStorageManager.DeleteFolderAsync(subFolder, true).ConfigureAwait(false);
                        codeResult.ProcessedFolderCount += 1;
                    }
                }
            }

            // Clean build folders
            if (cleanBuildFolders)
            {
                files = await Sidvall.SystemContext.Current.SystemStorageManager.GetFileNamesAsync(folderPath, new string[] { "*.csproj" }, Sidvall.IO.SearchOption.AllDirectories).ConfigureAwait(false);
                foreach (var file in files)
                {
                    fileFolder = System.IO.Path.GetDirectoryName(file);
                    subFolders = await Sidvall.SystemContext.Current.SystemStorageManager.GetFolderNamesAsync(fileFolder, new string[] { "bin", "obj" }, Sidvall.IO.SearchOption.TopDirectoryOnly).ConfigureAwait(false);
                    foreach (var subFolder in subFolders)
                    {
                        await Sidvall.SystemContext.Current.SystemStorageManager.DeleteFolderAsync(subFolder, true).ConfigureAwait(false);
                        codeResult.ProcessedFolderCount += 1;
                    }
                }
            }

            // Clean backup folders
            if (cleanBackupFolders)
            {
                files = await Sidvall.SystemContext.Current.SystemStorageManager.GetFileNamesAsync(folderPath, new string[] { "*.csproj" }, Sidvall.IO.SearchOption.AllDirectories).ConfigureAwait(false);
                foreach (var file in files)
                {
                    fileFolder = System.IO.Path.GetDirectoryName(file);
                    subFolders = await Sidvall.SystemContext.Current.SystemStorageManager.GetFolderNamesAsync(fileFolder, new string[] { "backup" }, Sidvall.IO.SearchOption.TopDirectoryOnly).ConfigureAwait(false);
                    foreach (var subFolder in subFolders)
                    {
                        await Sidvall.SystemContext.Current.SystemStorageManager.DeleteFolderAsync(subFolder, true).ConfigureAwait(false);
                        codeResult.ProcessedFolderCount += 1;
                    }
                }
            }

            // Remove source control links
            if (removeSourceControlLinks)
            {
                files = await Sidvall.SystemContext.Current.SystemStorageManager.GetFileNamesAsync(folderPath, new string[] { "*.vssscc", "*.vspscc", "*.csproj.user", "*.suo" }, Sidvall.IO.SearchOption.AllDirectories).ConfigureAwait(false);
                foreach (var file in files)
                    await Sidvall.SystemContext.Current.SystemStorageManager.DeleteFileAsync(file).ConfigureAwait(false);

                files = await Sidvall.SystemContext.Current.SystemStorageManager.GetFileNamesAsync(folderPath, new string[] { "*.sln" }, Sidvall.IO.SearchOption.AllDirectories).ConfigureAwait(false);
                foreach (var file in files)
                {
                    isUpdated = false;
                    text = await Sidvall.SystemContext.Current.SystemStorageManager.ReadAllTextAsync(file).ConfigureAwait(false);
                    index1 = text.IndexOf("GlobalSection(SubversionScc)", System.StringComparison.OrdinalIgnoreCase);
                    if (index1 >= 0)
                    {
                        index2 = text.IndexOf("EndGlobalSection", index1, System.StringComparison.OrdinalIgnoreCase);
                        text = text.Substring(0, index1) + text.Substring(index2 + 16);
                        isUpdated = true;
                    }
                    index1 = text.IndexOf("GlobalSection(TeamFoundationVersionControl)", System.StringComparison.OrdinalIgnoreCase);
                    if (index1 >= 0)
                    {
                        index2 = text.IndexOf("EndGlobalSection", index1, System.StringComparison.OrdinalIgnoreCase);
                        text = text.Substring(0, index1) + text.Substring(index2 + 16);
                        isUpdated = true;
                    }
                    if (isUpdated)
                    {
                        await Sidvall.SystemContext.Current.SystemStorageManager.WriteAllTextAsync(file, text).ConfigureAwait(false);
                        codeResult.UpdatedFileCount += 1;
                    }
                    codeResult.ProcessedFileCount += 1;
                }

                files = await Sidvall.SystemContext.Current.SystemStorageManager.GetFileNamesAsync(folderPath, new string[] { "*.csproj" }, Sidvall.IO.SearchOption.AllDirectories).ConfigureAwait(false);
                foreach (var file in files)
                {
                    isUpdated = false;
                    text = await Sidvall.SystemContext.Current.SystemStorageManager.ReadAllTextAsync(file).ConfigureAwait(false);
                    document = XDocument.Parse(text);
                    elements = (from o in document.Descendants()
                                where (o.Name.LocalName.Contains("SccProjectName") || o.Name.LocalName.Contains("SccLocalPath") ||
                                        o.Name.LocalName.Contains("SccAuxPath") || o.Name.LocalName.Contains("SccProvider"))
                                select o);
                    if (elements.Any())
                    {
                        elements.Remove();
                        text = document.ToString();
                        isUpdated = true;
                    }
                    if (isUpdated)
                    {
                        await Sidvall.SystemContext.Current.SystemStorageManager.WriteAllTextAsync(file, text).ConfigureAwait(false);
                        codeResult.UpdatedFileCount += 1;
                    }
                    codeResult.ProcessedFileCount += 1;
                }
            }
        }

        #endregion
        #region GetFileContentAsync

        public static async Task<string> GetFileContentAsync(string filePath, string searchPatterns, bool caseSensitive)
        {
            System.Text.StringBuilder sb;
            string content;
            int currentPos, pos, row, rowPos1, rowPos2;

            if (string.IsNullOrWhiteSpace(filePath))
                throw new System.ArgumentException("You have to specify a file path.");
            if (string.IsNullOrWhiteSpace(searchPatterns))
                throw new System.ArgumentException("You have to specify a search pattern.");
            if (!await Sidvall.SystemContext.Current.SystemStorageManager.FileExistsAsync(filePath).ConfigureAwait(false))
                throw new System.ArgumentException(filePath + " does not exist.");

            sb = new System.Text.StringBuilder();
            currentPos = 0;
            content = await Sidvall.SystemContext.Current.SystemStorageManager.ReadAllTextAsync(filePath).ConfigureAwait(false);
            do
            {
                if (caseSensitive)
                    pos = content.IndexOf(searchPatterns, currentPos, System.StringComparison.Ordinal);
                else
                    pos = content.IndexOf(searchPatterns, currentPos, System.StringComparison.OrdinalIgnoreCase);
                if (pos >= 0)
                {
                    rowPos1 = pos;
                    row = 0;
                    do
                    {
                        rowPos1 = content.LastIndexOf("\n", rowPos1) - 1;
                        row += 1;
                    } while ((row < RowCount) && (rowPos1 > 0));
                    if (rowPos1 < 0)
                        rowPos1 = 0;
                    rowPos2 = pos;
                    row = 0;
                    do
                    {
                        rowPos2 = content.IndexOf("\n", rowPos2) + 1;
                        row += 1;
                    } while ((row < RowCount) && (rowPos2 > 0));
                    if (rowPos2 < 0)
                        rowPos2 = content.Length - 1;
                    if (sb.Length > 0)
                        sb.Append(System.Environment.NewLine + System.Environment.NewLine + System.Environment.NewLine);
                    sb.Append(content.Substring(rowPos1, rowPos2 - rowPos1).Trim());
                }
                currentPos = pos + searchPatterns.Length;
            } while (pos >= 0);

            if (sb.Length > 0)
                return sb.ToString();
            return content;
        }

        #endregion
        #region RenameAsync

        public static async Task<CodeResult> RenameAsync(string folderPath, NameItemCollection nameItems, string[] searchPatterns, bool renameFolders,
            bool renameFiles, bool updateFileContents, bool caseSensitive, bool recursiveFolders)
        {
            CodeResult codeResult;
            List<string> files;

            codeResult = new CodeResult();
            if (string.IsNullOrWhiteSpace(folderPath))
                throw new System.ArgumentException("You have to specify a root folder.");
            if (nameItems.Count == 0)
                throw new System.ArgumentException("You have to specify at least 1 old and new name.");
            if (!await Sidvall.SystemContext.Current.SystemStorageManager.FolderExistsAsync(folderPath).ConfigureAwait(false))
                throw new System.ArgumentException(folderPath + " does not exist.");

            if (renameFolders)
                await RenameFolderAsync(folderPath, nameItems, recursiveFolders).ConfigureAwait(false);
            if (renameFiles)
            {
                files = (await Sidvall.SystemContext.Current.SystemStorageManager.GetFileNamesAsync(folderPath, searchPatterns, Sidvall.IO.SearchOption.AllDirectories).ConfigureAwait(false)).ToList();
                foreach (var file in files)
                {
                    await RenameFileAsync(file, nameItems).ConfigureAwait(false);
                    codeResult.ProcessedFileCount += 1;
                }
            }
            if (updateFileContents)
            {
                files = (await Sidvall.SystemContext.Current.SystemStorageManager.GetFileNamesAsync(folderPath, searchPatterns, Sidvall.IO.SearchOption.AllDirectories).ConfigureAwait(false)).ToList();
                foreach (var file in files)
                {
                    var text = await Sidvall.SystemContext.Current.SystemStorageManager.ReadAllTextAsync(file).ConfigureAwait(false);
                    if (caseSensitive)
                        text = GetName(text, nameItems);
                    else
                        text = GetName(text, nameItems, System.StringComparison.OrdinalIgnoreCase);
                    await Sidvall.SystemContext.Current.SystemStorageManager.WriteAllTextAsync(file, text).ConfigureAwait(false);
                    if (!renameFiles)
                        codeResult.ProcessedFileCount += 1;
                }
            }

            return codeResult;
        }

        #endregion
        #region SearchAsync

        public static async Task<CodeResult> SearchAsync(string folderPath, string[] fileSearchPatterns, string searchPatterns,
            bool caseSensitive, bool searchFolderName, bool searchFileName, bool searchFileContent, bool loadDetails)
        {
            CodeResult codeResult;
            PathName item;

            codeResult = new CodeResult();
            if (string.IsNullOrWhiteSpace(folderPath))
                throw new System.ArgumentException("You have to specify a root folder.");
            if ((searchFileContent) && (string.IsNullOrWhiteSpace(searchPatterns)))
                throw new System.ArgumentException("You have to specify a search pattern.");
            if (!await Sidvall.SystemContext.Current.SystemStorageManager.FolderExistsAsync(folderPath).ConfigureAwait(false))
                throw new System.ArgumentException(folderPath + " does not exist.");

            if ((searchFileName) || (searchFileContent))
            {
                var files = await Sidvall.SystemContext.Current.SystemStorageManager.GetFilesAsync(folderPath, fileSearchPatterns, Sidvall.IO.SearchOption.AllDirectories, loadDetails).ConfigureAwait(false);
                foreach (var file in files)
                {
                    item = await ProcessFileAsync(file, searchPatterns, caseSensitive, searchFileName, searchFileContent).ConfigureAwait(false);
                    if (item != null)
                        codeResult.PathNames.Add(item);
                    codeResult.ProcessedFileCount += 1;
                }
            }
            else if (searchFolderName)
            {
                var folders = await Sidvall.SystemContext.Current.SystemStorageManager.GetFoldersAsync(folderPath, fileSearchPatterns, Sidvall.IO.SearchOption.AllDirectories, loadDetails).ConfigureAwait(false);
                foreach (var folder in folders)
                {
                    item = ProcessFolder(folder, searchPatterns, caseSensitive);
                    if (item != null)
                        codeResult.PathNames.Add(item);
                    codeResult.ProcessedFolderCount += 1;
                }
            }
            return codeResult;
        }

        #endregion

        #endregion
        #region Private Members

        private const int RowCount = 3;

        #region GetName

        private static string GetName(string sourceName, NameItemCollection nameItems)
        {
            return GetName(sourceName, nameItems, System.StringComparison.Ordinal);
        }
        private static string GetName(string sourceName, NameItemCollection nameItems, System.StringComparison comparison)
        {
            foreach (var nameItem in nameItems)
                sourceName = GetName(sourceName, nameItem.OldName, nameItem.NewName, comparison);
            return sourceName;
        }
        private static string GetName(string sourceName, string oldName, string newName)
        {
            return GetName(sourceName, oldName, newName, System.StringComparison.Ordinal);
        }
        private static string GetName(string sourceName, string oldName, string newName, System.StringComparison comparison)
        {
            string tmp;
            int? oldValue, newValue;
            int pos;

            if (oldName.Contains(NumberPath))
            {
                tmp = sourceName;
                pos = tmp.LastIndexOf('\\');
                if (pos >= 0)
                    tmp = sourceName.Substring(pos + 1);
                pos = tmp.LastIndexOf('.');
                if (pos >= 0)
                    tmp = sourceName.Substring(0, pos);
                oldValue = tmp.ToNullableInt32();
                if (oldValue != null)
                {
                    newValue = oldValue.Value + newName.ToNullableInt32().Value;
                    oldName = tmp;
                    newName = newValue.ToString();
                }
                else
                {
                    return sourceName;
                }
            }
            return sourceName.Replace(oldName, newName, comparison);
        }

        #endregion
        #region GetSolutionFoldersAsync

        private static async Task<List<string>> GetSolutionFoldersAsync(string folderPath)
        {
            var folders = new List<string>();
            var files = await Sidvall.SystemContext.Current.SystemStorageManager.GetFileNamesAsync(folderPath, new string[] { "*.sln" }, Sidvall.IO.SearchOption.AllDirectories).ConfigureAwait(false);
            if (files != null)
            {
                foreach (var file in files)
                    folders.Add(System.IO.Path.GetDirectoryName(file));
            }
            return folders;
        }

        #endregion
        #region ProcessFileAsync

        private static async Task<PathName> ProcessFileAsync(Sidvall.IO.File item, string searchPatterns, bool caseSensitive, bool searchFileName, bool searchFileContent)
        {
            string content;
            bool isFound;

            isFound = false;
            if ((!isFound) && (searchFileName))
            {
                if (!string.IsNullOrWhiteSpace(searchPatterns))
                {
                    if (caseSensitive)
                        isFound = item.Name.Contains(searchPatterns, System.StringComparison.Ordinal);
                    else
                        isFound = item.Name.Contains(searchPatterns, System.StringComparison.OrdinalIgnoreCase);
                }
                else
                {
                    isFound = true;
                }
            }
            if ((!isFound) && (searchFileContent))
            {
                content = await Sidvall.SystemContext.Current.SystemStorageManager.ReadAllTextAsync(item.FullName).ConfigureAwait(false);
                if (caseSensitive)
                    isFound = content.Contains(searchPatterns, System.StringComparison.Ordinal);
                else
                    isFound = content.Contains(searchPatterns, System.StringComparison.OrdinalIgnoreCase);
            }
            if (isFound)
            {
                return new PathName()
                {
                    Name = item.Name,
                    FullName = item.FullName,
                    Folder = System.IO.Path.GetDirectoryName(item.FullName),
                    Size = item.Size,
                    IsFolder = false,
                };
            }
            return null;
        }

        #endregion
        #region ProcessFolder

        private static PathName ProcessFolder(Sidvall.IO.Folder item, string searchPatterns, bool caseSensitive)
        {
            bool isFound;

            if (!string.IsNullOrWhiteSpace(searchPatterns))
            {
                if (caseSensitive)
                    isFound = item.Name.Contains(searchPatterns, System.StringComparison.Ordinal);
                else
                    isFound = item.Name.Contains(searchPatterns, System.StringComparison.OrdinalIgnoreCase);
            }
            else
            {
                isFound = true;
            }
            if (isFound)
            {
                return new PathName()
                {
                    Name = item.Name,
                    FullName = item.FullName,
                    Folder = System.IO.Path.GetDirectoryName(item.FullName),
                    IsFolder = true,
                };
            }
            return null;
        }

        #endregion
        #region RenameFileAsync

        private static async Task RenameFileAsync(string sourceFileName, NameItemCollection nameItems)
        {
            var newFileName = GetName(sourceFileName, nameItems);
            if (!newFileName.Equals(sourceFileName, System.StringComparison.OrdinalIgnoreCase))
                await Sidvall.SystemContext.Current.SystemStorageManager.MoveFileAsync(sourceFileName, newFileName, false).ConfigureAwait(false);
        }

        #endregion
        #region RenameFolderAsync

        private static async Task RenameFolderAsync(string sourceFolderName, NameItemCollection nameItems, bool recursive)
        {
            var newFolderName = GetName(sourceFolderName, nameItems);
            if (!newFolderName.Equals(sourceFolderName, System.StringComparison.OrdinalIgnoreCase))
                await Sidvall.SystemContext.Current.SystemStorageManager.MoveFolderAsync(sourceFolderName, newFolderName, false).ConfigureAwait(false);
            if (recursive)
            {
                var folders = (await Sidvall.SystemContext.Current.SystemStorageManager.GetFolderNamesAsync(newFolderName, null, Sidvall.IO.SearchOption.TopDirectoryOnly).ConfigureAwait(false)).ToList();
                foreach (var subFolder in folders)
                    await RenameFolderAsync(subFolder, nameItems, recursive).ConfigureAwait(false);
            }
        }

        #endregion

        #endregion
    }
}
